Jelajahi fitur TypeScript tingkat lanjut seperti tipe template literal dan kondisional untuk menulis kode yang lebih ekspresif dan mudah dipelihara. Kuasai manipulasi tipe untuk skenario kompleks.
Tipe Tingkat Lanjut TypeScript: Menguasai Tipe Template Literal dan Kondisional
Kekuatan TypeScript terletak pada sistem tipenya yang kuat. Meskipun tipe dasar seperti string, number, dan boolean sudah cukup untuk banyak skenario, fitur-fitur canggih seperti tipe template literal dan tipe kondisional membuka tingkat ekspresivitas dan keamanan tipe yang baru. Panduan ini memberikan gambaran komprehensif tentang tipe-tipe tingkat lanjut ini, menjelajahi kemampuannya dan menunjukkan aplikasi praktisnya.
Memahami Tipe Template Literal
Tipe template literal dibangun di atas template literal JavaScript, memungkinkan Anda mendefinisikan tipe berdasarkan interpolasi string. Ini memungkinkan pembuatan tipe yang merepresentasikan pola string tertentu, membuat kode Anda lebih kuat dan dapat diprediksi.
Sintaks Dasar dan Penggunaan
Tipe template literal menggunakan backtick (`) untuk menyertakan definisi tipe, mirip dengan template literal JavaScript. Di dalam backtick, Anda dapat menginterpolasi tipe lain menggunakan sintaks ${}. Di sinilah keajaibannya terjadi – Anda pada dasarnya membuat tipe yang merupakan string, dibangun pada waktu kompilasi berdasarkan tipe di dalam interpolasi.
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIEndpoint = `/api/${string}`;
// Contoh Penggunaan
const getEndpoint: APIEndpoint = "/api/users"; // Valid
const postEndpoint: APIEndpoint = "/api/products/123"; // Valid
const invalidEndpoint: APIEndpoint = "/admin/settings"; // TypeScript tidak akan menunjukkan kesalahan di sini karena `string` bisa berupa apa saja
Dalam contoh ini, APIEndpoint adalah tipe yang merepresentasikan string apa pun yang diawali dengan /api/. Meskipun contoh dasar ini berguna, kekuatan sebenarnya dari tipe template literal muncul saat digabungkan dengan batasan tipe yang lebih spesifik.
Menggabungkan dengan Tipe Union
Tipe template literal benar-benar bersinar saat digunakan dengan tipe union. Ini memungkinkan Anda membuat tipe yang merepresentasikan sekumpulan kombinasi string tertentu.
type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
type APIPath = "users" | "products" | "orders";
type APIEndpoint = `/${APIPath}/${HTTPMethod}`;
// Endpoint API yang Valid
const getUsers: APIEndpoint = "/users/GET";
const postProducts: APIEndpoint = "/products/POST";
// Endpoint API yang Tidak Valid (akan menghasilkan kesalahan TypeScript)
// const invalidEndpoint: APIEndpoint = "/users/PATCH"; // Error: "/users/PATCH" tidak dapat ditetapkan ke tipe "/users/GET" | "/users/POST" | "/users/PUT" | "/users/DELETE" | "/products/GET" | "/products/POST" | ... 3 lagi ... | "/orders/DELETE".
Sekarang, APIEndpoint adalah tipe yang lebih ketat yang hanya mengizinkan kombinasi jalur API dan metode HTTP tertentu. TypeScript akan menandai setiap upaya untuk menggunakan kombinasi yang tidak valid, meningkatkan keamanan tipe.
Manipulasi String dengan Tipe Template Literal
TypeScript menyediakan tipe manipulasi string intrinsik yang bekerja secara mulus dengan tipe template literal. Tipe-tipe ini memungkinkan Anda untuk mengubah string pada waktu kompilasi.
- Uppercase: Mengubah string menjadi huruf besar.
- Lowercase: Mengubah string menjadi huruf kecil.
- Capitalize: Mengkapitalisasi huruf pertama dari sebuah string.
- Uncapitalize: Menghilangkan kapitalisasi huruf pertama dari sebuah string.
type Greeting = "hello world";
type UppercaseGreeting = Uppercase; // "HELLO WORLD"
type LowercaseGreeting = Lowercase; // "hello world"
type CapitalizedGreeting = Capitalize; // "Hello world"
type UncapitalizedGreeting = Uncapitalize; // "hello world"
Tipe manipulasi string ini sangat berguna untuk secara otomatis menghasilkan tipe berdasarkan konvensi penamaan. Misalnya, Anda bisa menurunkan tipe aksi dari nama event atau sebaliknya.
Aplikasi Praktis Tipe Template Literal
- Definisi Endpoint API: Seperti yang ditunjukkan di atas, mendefinisikan endpoint API dengan batasan tipe yang presisi.
- Penanganan Event: Membuat tipe untuk nama event dengan awalan dan akhiran tertentu.
- Generasi Kelas CSS: Menghasilkan nama kelas CSS berdasarkan nama dan status komponen.
- Pembuatan Kueri Basis Data: Memastikan keamanan tipe saat membangun kueri basis data.
Contoh Internasional: Pemformatan Mata Uang
Bayangkan membangun aplikasi keuangan yang mendukung banyak mata uang. Anda dapat menggunakan tipe template literal untuk memberlakukan pemformatan mata uang yang benar.
type CurrencyCode = "USD" | "EUR" | "GBP" | "JPY";
type CurrencyFormat = `${number} ${T}`;
const priceUSD: CurrencyFormat<"USD"> = "100 USD"; // Valid
const priceEUR: CurrencyFormat<"EUR"> = "50 EUR"; // Valid
// const priceInvalid: CurrencyFormat<"USD"> = "100 EUR"; // Error: Tipe 'string' tidak dapat ditetapkan ke tipe '`${number} USD`'.
function formatCurrency(amount: number, currency: T): CurrencyFormat {
return `${amount} ${currency}`;
}
const formattedUSD = formatCurrency(250, "USD"); // Tipe: "250 USD"
const formattedEUR = formatCurrency(100, "EUR"); // Tipe: "100 EUR"
Contoh ini memastikan bahwa nilai mata uang selalu diformat dengan kode mata uang yang benar, mencegah potensi kesalahan.
Menyelami Tipe Kondisional
Tipe kondisional memperkenalkan logika percabangan ke dalam sistem tipe TypeScript, memungkinkan Anda mendefinisikan tipe yang bergantung pada tipe lain. Fitur ini sangat kuat untuk membuat definisi tipe yang sangat fleksibel dan dapat digunakan kembali.
Sintaks Dasar dan Penggunaan
Tipe kondisional menggunakan kata kunci infer dan operator ternary (kondisi ? tipeBenar : tipeSalah) untuk mendefinisikan kondisi tipe.
type IsString = T extends string ? true : false;
type StringCheck = IsString; // tipe StringCheck = true
type NumberCheck = IsString; // tipe NumberCheck = false
Dalam contoh ini, IsString adalah tipe kondisional yang memeriksa apakah T dapat ditetapkan ke string. Jika ya, tipe akan menjadi true; jika tidak, akan menjadi false.
Kata Kunci infer
Kata kunci infer memungkinkan Anda mengekstrak tipe dari tipe lain. Ini sangat berguna saat bekerja dengan tipe kompleks seperti tipe fungsi atau tipe array.
type ReturnType any> = T extends (...args: any) => infer R ? R : any;
function add(a: number, b: number): number {
return a + b;
}
type AddReturnType = ReturnType; // tipe AddReturnType = number
Dalam contoh ini, ReturnType mengekstrak tipe kembalian dari tipe fungsi T. Bagian infer R dari tipe kondisional menginferensikan tipe kembalian dan menugaskannya ke variabel tipe R. Jika T bukan tipe fungsi, tipe akan menjadi any.
Tipe Kondisional Distributif
Tipe kondisional menjadi distributif ketika tipe yang diperiksa adalah parameter tipe telanjang (naked type parameter). Ini berarti tipe kondisional diterapkan ke setiap anggota tipe union secara terpisah.
type ToArray = T extends any ? T[] : never;
type NumberOrStringArray = ToArray; // tipe NumberOrStringArray = string[] | number[]
Dalam contoh ini, ToArray mengubah tipe T menjadi tipe array. Karena T adalah parameter tipe telanjang (tidak dibungkus dalam tipe lain), tipe kondisional diterapkan pada number dan string secara terpisah, menghasilkan union dari number[] dan string[].
Aplikasi Praktis Tipe Kondisional
- Mengekstrak Tipe Kembalian: Seperti yang ditunjukkan di atas, mengekstrak tipe kembalian dari sebuah fungsi.
- Menyaring Tipe dari Union: Membuat tipe yang hanya berisi tipe-tipe tertentu dari sebuah union.
- Mendefinisikan Tipe Fungsi Overload: Membuat tipe fungsi yang berbeda berdasarkan tipe input.
- Membuat Type Guard: Mendefinisikan fungsi yang mempersempit tipe dari sebuah variabel.
Contoh Internasional: Menangani Format Tanggal yang Berbeda
Berbagai wilayah di dunia menggunakan format tanggal yang berbeda. Anda dapat menggunakan tipe kondisional untuk menangani variasi ini.
type DateFormat = "YYYY-MM-DD" | "MM/DD/YYYY" | "DD.MM.YYYY";
type ParseDate = T extends "YYYY-MM-DD"
? { year: number; month: number; day: number; format: "YYYY-MM-DD" }
: T extends "MM/DD/YYYY"
? { month: number; day: number; year: number; format: "MM/DD/YYYY" }
: T extends "DD.MM.YYYY"
? { day: number; month: number; year: number; format: "DD.MM.YYYY" }
: never;
function parseDate(dateString: string, format: T): ParseDate {
// (Implementasi akan menangani format tanggal yang berbeda)
if (format === "YYYY-MM-DD") {
const [year, month, day] = dateString.split("-").map(Number);
return { year, month, day, format } as ParseDate;
} else if (format === "MM/DD/YYYY") {
const [month, day, year] = dateString.split("/").map(Number);
return { month, day, year, format } as ParseDate;
} else if (format === "DD.MM.YYYY") {
const [day, month, year] = dateString.split(".").map(Number);
return { day, month, year, format } as ParseDate;
} else {
throw new Error("Invalid date format");
}
}
const parsedDateISO = parseDate("2023-10-27", "YYYY-MM-DD"); // Tipe: { year: number; month: number; day: number; format: "YYYY-MM-DD"; }
const parsedDateUS = parseDate("10/27/2023", "MM/DD/YYYY"); // Tipe: { month: number; day: number; year: number; format: "MM/DD/YYYY"; }
const parsedDateEU = parseDate("27.10.2023", "DD.MM.YYYY"); // Tipe: { day: number; month: number; year: number; format: "DD.MM.YYYY"; }
console.log(parsedDateISO.year); // Mengakses tahun dengan mengetahui itu akan ada
Contoh ini menggunakan tipe kondisional untuk mendefinisikan fungsi parsing tanggal yang berbeda berdasarkan format tanggal yang ditentukan. Tipe ParseDate memastikan bahwa objek yang dikembalikan memiliki properti yang benar berdasarkan formatnya.
Menggabungkan Tipe Template Literal dan Kondisional
Kekuatan sebenarnya datang ketika Anda menggabungkan tipe template literal dan tipe kondisional. Ini memungkinkan manipulasi tipe yang sangat kuat.
type EventName = `on${Capitalize}`;
type ExtractEventPayload = T extends EventName
? { type: T; payload: any } // Disederhanakan untuk demonstrasi
: never;
type ClickEvent = EventName<"click">; // "onClick"
type MouseOverEvent = EventName<"mouseOver">; // "onMouseOver"
//Contoh fungsi yang mengambil tipe
function processEvent(event: T): ExtractEventPayload {
//Dalam implementasi nyata, kita akan benar-benar mengirimkan event tersebut.
console.log(`Memproses event ${event}`);
//Dalam implementasi nyata, payload akan didasarkan pada tipe event.
return { type: event, payload: {} } as ExtractEventPayload;
}
//Perhatikan bahwa tipe kembaliannya sangat spesifik:
const clickEvent = processEvent("onClick"); // { type: "onClick"; payload: any; }
const mouseOverEvent = processEvent("onMouseOver"); // { type: "onMouseOver"; payload: any; }
//Jika Anda menggunakan string lain, Anda mendapatkan never:
// const someOtherEvent = processEvent("someOtherEvent"); // Tipe adalah `never`
Praktik Terbaik dan Pertimbangan
- Jaga Tetap Sederhana: Meskipun kuat, tipe-tipe tingkat lanjut ini bisa menjadi kompleks dengan cepat. Usahakan untuk kejelasan dan kemudahan pemeliharaan.
- Uji Secara Menyeluruh: Pastikan definisi tipe Anda berperilaku seperti yang diharapkan dengan menulis unit test yang komprehensif.
- Dokumentasikan Kode Anda: Dokumentasikan dengan jelas tujuan dan perilaku tipe tingkat lanjut Anda untuk meningkatkan keterbacaan kode.
- Pertimbangkan Performa: Penggunaan tipe tingkat lanjut yang berlebihan dapat memengaruhi waktu kompilasi. Profil kode Anda dan optimalkan jika perlu.
Kesimpulan
Tipe template literal dan tipe kondisional adalah alat yang kuat dalam persenjataan TypeScript. Dengan menguasai tipe-tipe tingkat lanjut ini, Anda dapat menulis kode yang lebih ekspresif, mudah dipelihara, dan aman secara tipe. Fitur-fitur ini memungkinkan Anda untuk menangkap hubungan kompleks antar tipe, memberlakukan batasan yang lebih ketat, dan membuat definisi tipe yang sangat dapat digunakan kembali. Manfaatkan teknik-teknik ini untuk meningkatkan keterampilan TypeScript Anda dan membangun aplikasi yang kuat dan skalabel untuk audiens global.